package ipc;

import graph.Graph;

import java.io.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.locks.Lock;

import dd.discrete.ADD;
import dd.discrete.ADDDNode;
import dd.discrete.ADDINode;
import dd.discrete.ADDNode;
import dd.discrete.DD;

import rddl.*;
import rddl.RDDL.PVAR_INST_DEF;
import rddl.RDDL.*;
import rddl.competition.Client;
import rddl.parser.*;
import rddl.policy.Policy;
import rddl.viz.RDDL2Graph;
import util.*;
import rddl.State;

public class ValueIteration implements Runnable{

	public final static int     STATE_ITER  = 0;
	public final static int     OBSERV_ITER = 1;
	
	public static double EPSILON = 0.1;
	
	public static final int ORDER_XXP = 1;
	public static final int ORDER_XPX = 2;
	public static final int ORDER_GUESS = 7;
	public static final int ORDER_INTERLEAVE = 8;
	
	public State      _state;
	public INSTANCE   _i;
	public NONFLUENTS _n;
	public DOMAIN     _d;
	
	public ADD         _context;
	public ArrayList<Integer> _alSaveNodes;
	public int         DD_ZERO;
	public int         DD_ONE;

	public TreeMap<String, Pair<PVAR_NAME, ArrayList<LCONST> > > _hmStateVars;
	public TreeMap<String, Pair<PVAR_NAME, ArrayList<LCONST> > > _hmNextStateVars;
	public TreeMap<String,ArrayList<PVAR_INST_DEF>> _hmActionMap;
	public TreeMap<String, Pair<PVAR_NAME, ArrayList<LCONST> > > _hmObservVars;
	public HashMap<String,String> _hmPrimeRemap;
	
	public TreeMap<Pair, Integer> _var2transDD;
	public TreeMap<Pair, Integer> _var2observDD;
	public TreeMap<String, Integer> _act2rewardDD;
	
	private int _valueDD;
	private int _rewardDD;
	
	private int _order;
	private ArrayList<String> _alOrder;
	
	public int _policyDD;
	
	private Map<String, Integer> _hmAct2ID = new HashMap<String, Integer>();
	
	private Map<Integer, String> _hmID2Act = new HashMap<Integer, String>();
	private Policy policy;
	
	public List<iterationData> iData = new ArrayList<iterationData>(); // :-)
	public preSolve presolve;
	
	public double avgR;
	public double varR;
	
	public long timetoplay;
	public postSolve postsolve;
	private TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars;
	
	public runInfo rInfo;
	
	private TreeSet<Pair<Integer, Integer>> _hsPairConstraints = new TreeSet<Pair<Integer,Integer>>();
	private ArrayList<String> _alactionvars = new ArrayList<String>();
	private ArrayList<String> _alstatevars = new ArrayList<String>();
	private ArrayList<String> _alnextstatevars = new ArrayList<String>();
	private ArrayList<String> _alObservvars = new ArrayList<String>();
	private RDDL	_rddl;
	private Integer	_ndegree;
	private String	_sDestDir;
	public static final double BIGADD = 1e6;
	private static final boolean	DEBUG0	= true;
	private static final boolean	DEBUG2	= true;
	private static final boolean	DEBUG4	= false;
	private static final Runtime	RUNTIME	=  Runtime.getRuntime() ;
	
	private ArrayBlockingQueue<Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> _bq;
	private ArrayList<String>	_alSumOrder;
	
	public ValueIteration(String domain_file, String instance_file, 
			final Double epsilon, final Integer order, 
			final Integer degree, final String dest_dir,
			ArrayBlockingQueue<Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> pBq1
			) throws Exception {
		_order= order;
		EPSILON = epsilon;
		
		RDDL _rddltemp = new RDDL();
		
		File domf = new File(domain_file);
		File instf = new File(instance_file);
		
		_rddltemp.addOtherRDDL(parser.parse(domf));
		_rddltemp.addOtherRDDL(parser.parse(instf));
		
		_rddl = _rddltemp;
		_ndegree = degree;
		_sDestDir = dest_dir;
		_bq = pBq1;
	}
	
	public ValueIteration(RDDL rddl, final double epsilon, final int order, final int degree,
			 final String dest_dir,
			 ArrayBlockingQueue<Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> bq) throws Exception{
		_order = order;
		EPSILON = epsilon;
		
		_rddl = rddl;
		_ndegree = degree;
		_sDestDir = dest_dir;
		_bq = bq;
	}
	
	public ArrayList<String> setupVI(){
		
		try {
			
			RDDL _rddltemp = _rddl;
			int degree = _ndegree;
			String dest_dir = _sDestDir; 
			
			this._d= _rddltemp._tmDomainNodes.entrySet().iterator().next().getValue();
			this._i= _rddltemp._tmInstanceNodes.entrySet().iterator().next().getValue();
			this._n= _rddltemp._tmNonFluentNodes.entrySet().iterator().next().getValue();
			
			if( degree != -1){
				_i._nNonDefActions = degree;
			}
			
			rInfo = new runInfo("ADD", _i._sName, _i._nNonDefActions, _d._sDomainName);
			
			_state = new State();
			
			_state.init(_n != null ? _n._hmObjects : null, _i._hmObjects,  
					_d._hmTypes, _d._hmPVariables, _d._hmCPF,
					_i._alInitState, _n == null ? null : _n._alNonFluents, 
					_d._alStateConstraints, _d._exprReward, _i._nNonDefActions);
			
			long _time = System.currentTimeMillis();
			// Build the CPTs
			buildCPTs();
			
	//		System.out.println(_hmAct2ID);
			
			
			presolve = new preSolve( getSizeofR(), getSizeofP(), getSizeofA(), (System.currentTimeMillis()-_time)/(1000d*60));
			
			System.out.println(rInfo);
			System.out.println(presolve);
			
			ArrayList<String> _alSumOrder = new ArrayList<String>();
			makeOrders(_alSumOrder);
		
			return _alSumOrder;
		}catch(Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
//	@Override
	public void run(){
		
		_alSumOrder = setupVI();
		//val iteration begin
		
		_valueDD = DD_ZERO;
		
		int iter = 0;
		boolean done = false;

		long timetosolve = 0;
		
		while ( !done ) {

			long itertime = System.currentTimeMillis();
			
			int Vprev = _valueDD; 
			int Vprimed = _context.remapGIDsInt(Vprev, _hmPrimeRemap);
			
			if( !_context.verifyOrder(Vprimed) ) {
				Vprimed = _context.align(Vprimed);
			}
			
			if( !_context.verifyOrder(Vprimed) ) {
				System.err.println("V' violates order");
				System.exit(1);
			}
			
			//SANITY Check : Vprimed does not contain unprimed variables
			if( DEBUG0 ) {
				Set nodes = _context.getGIDs(Vprimed);
				for( String s : _alstatevars ) {
					if( nodes.contains((Integer)(_context._hmVarName2ID.get(s) ) ) ) {
						System.err.println("V' contains x");
						System.exit(1);
					}
				}
				for( String a : _alactionvars ) {
					if( nodes.contains((Integer)(_context._hmVarName2ID.get(a) ) ) ) {
						System.err.println("V' contains a");
						System.exit(1);
					}
				}
			}
			
			if( DEBUG4 ) {
				System.out.println("Displaying V'");
				_context.getGraph(Vprimed).launchViewer();
				System.out.println("Press key");
				try
				{
					System.in.read();
				} catch (IOException e)
				{
					e.printStackTrace();
				}
			}
			
			Set gids = _context.getGIDs(Vprimed);
			
			for( String act : _hmActionMap.keySet() ){
			
//				System.out.println(_context.countExactNodes(Vprimed));
				
				if( DEBUG0 ) {
					System.out.println("Regressing via " + act);
				}
			
				int temp = Vprimed;
//				System.out.println(act);
				
				if( DEBUG2 ) {
					System.out.println("size of V' before zero constraint : " + _context.countExactNodes(temp));	
				}
				
//				_context.getGraph(temp).launchViewer();
				temp = applyNegInfConstraints(temp, act, action_vars, Vprev, Vprimed);
//				_context.getGraph(temp).launchViewer();
//				System.out.println(_context.countExactNodes(Vprimed));
				
				if( DEBUG2 ) {
					System.out.println("size of V' after constraint : " + _context.countExactNodes(temp));	
				}
				
				if( DEBUG4 ) {
					System.out.println("size of V' after constraint : " + _context.countExactNodes(temp));
					System.out.println("Displaying V' after zero constraints");
					_context.getGraph(temp).launchViewer();
					System.out.println("Press key");
					try
					{
						System.in.read();
					} catch (IOException e)
					{
						e.printStackTrace();
					}
				}
				
//				_context.getGraph(Vprimed).launchViewer();
//				Thread.sleep(1000000000);
				
				for( String x : _alSumOrder ){
					String xprimed = x;
					int xprimedid = (Integer)(_context._hmVarName2ID.get(xprimed));
					
					if( ! gids.contains(xprimedid) ){
						if( DEBUG2 ) {
							System.out.println("Skipped " + xprimed);	
						}
						continue;
					}
					
					int dualdd = _var2transDD.get(new Pair(act, x));

					if( DEBUG2 ) {
						System.out.println("Multiplying " + xprimed);
					}
					
					temp = _context.applyInt(temp, dualdd, DD.ARITH_PROD );
					
					if( DEBUG2 ) {
						System.out.println("Summing " + xprimed);
					}
					
					temp = _context.opOut(temp, xprimedid , DD.ARITH_SUM );
					
					if( DEBUG2 ) {
						System.out.println("Expectation " + xprimed);
						System.out.println(" size: " + _context.countExactNodes(temp));
						System.out.println(showMemory());
					}
					
					flushCaches(temp, Vprev, Vprimed);
					
				}
				
//				System.out.println(_context.countExactNodes(Vprimed));
				
				
				if( DEBUG0 ) {
					Set nodes = _context.getGIDs(temp);
					for( String ns : _alnextstatevars ) {
						if( nodes.contains((Integer)(_context._hmVarName2ID.get(ns) ) ) ) {
							System.err.println("EV' contains x'");
							System.err.println("Displaying V'");
							_context.getGraph(temp).launchViewer();
							System.out.println("Press key");
							try
							{
								System.in.read();
							} catch (IOException e)
							{
								e.printStackTrace();
							}
							System.exit(1);
						}
					}
				}
				
//				_context.getGraph(temp).launchViewer();
//				Thread.sleep(1000000000);
//				System.out.println(temp == DD_ZERO);
				
				temp = _context.scalarMultiply(temp, _i._dDiscount);
				
				if( DEBUG2 ) {
					System.out.println("Size before making negative infs. to EV'");
					System.out.println(_context.countExactNodes(temp));
				}
				
//				System.out.println(_context.countExactNodes(Vprimed));
				
				
//				temp = applyConstraintsInterleaved(temp, act, action_vars, Vprev, Vprimed);
				
//				System.out.println(_context.countExactNodes(Vprimed));
				
				if( DEBUG2 ) {
					System.out.println("Size after making negative infs. to EV'");
					System.out.println(_context.countExactNodes(temp));
				}
				
				if( DEBUG0 ) {
					System.out.println("Adding reward");
				}
				
				temp = _context.applyInt(temp, _act2rewardDD.get(act), DD.ARITH_SUM );
				
				temp = _context.applyInt(temp, _rewardDD, DD.ARITH_SUM );
				
				if( DEBUG2 ) {
					System.out.println("Size of V before constraints " + _context.countExactNodes(temp));
					System.out.println(showMemory());
				}
				
//				System.out.println(_context.countExactNodes(Vprimed));
				
//				temp = applyConstraintsInterleaved(temp, act, action_vars, Vprev);

				int actid = _hmAct2ID.get(act);

//				System.out.println(act + " " + actid);
				if( DEBUG0 ) {
					System.out.println("MAXing");
				}
				
//				System.out.println(_context.countExactNodes(Vprimed));
				
				
				int Vnext = _context.applyInt(_valueDD, temp, DD.ARITH_MAX);
				
				if( DEBUG2 ) {
					System.out.println("Updating policy");
				}
				
				int interdiff = _context.applyInt(Vnext, _valueDD, DD.ARITH_MINUS);
				
				interdiff = _context.AddtoBdd(interdiff, 0d);
				
				_policyDD = _context.applyInt(
								_context.applyInt(
										_policyDD, 
										_context.applyInt(
												DD_ONE, 
												interdiff, 
												DD.ARITH_MINUS), 
										DD.ARITH_PROD)
								,_context.scalarMultiply(interdiff, actid), 
								DD.ARITH_SUM);
				
//				_context.getGraph(interdiff).launchViewer();
				
				_valueDD = Vnext;
				
//				System.out.println(_context.countExactNodes(Vprimed));
				
				flushCaches(Vprev, Vprimed);
				
			}
			
			int diff  = _context.applyInt(_valueDD, Vprev, DD.ARITH_MINUS);
			
//			_context.getGraph(diff).launchViewer();
//			System.in.read();
//			_context.setMinMax(diff);
			double max_pos_diff = _context.getMaxValue(diff);
            double max_neg_diff = _context.getMinValue(diff);
            double error = Math.max(Math.abs(max_pos_diff), Math.abs(max_neg_diff));

            ++iter;
            
            if( iter == _i._nHorizon || error <= EPSILON ) {
				done = true;
			}

			flushCaches();
			itertime = (System.currentTimeMillis()-itertime);
			
			
			System.out.println("iteration " + iter + " BE = " + error);
			
			iData.add( new iterationData(iter, getSizeofV(), getSizeofPi(), itertime/(1000d*60), error, 0 ) );
			timetosolve += itertime;
			
			
		}
		
		if( _bq != null ) {
			try
			{
//				_bq.put(new Pair<ADD,Integer>(_context, _valueDD));
				_bq.put(new Pair<ADD, Pair<Integer,HashMap<Long,TreeMap<String,Boolean>>>>
					(_context, 
							new Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>
							(_valueDD, null)));
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}				
		}
		
		playDD();
		
		postsolve = new postSolve(avgR, varR, getSizeofV(), getSizeofPi(), timetoplay/(1000d*60), timetosolve/(1000d*60));
	}
	
	private HashMap<Long, TreeMap<String, Boolean>> makeDiffMap() {
		
		HashMap<Long, TreeMap<String, Boolean>> ret = new HashMap<Long, TreeMap<String,Boolean>>();
		
		for( Map.Entry<Integer, String> entry : _hmID2Act.entrySet()) {
			ArrayList<PVAR_INST_DEF> actdetails = _hmActionMap.get(entry.getValue());
			TreeMap<String, Boolean> assigns = new TreeMap<String, Boolean>();
			for( PVAR_INST_DEF pid : actdetails ) {
				assigns.put(CleanFluentName(pid._sPredName.toString() + pid._alTerms), 
						(Boolean)pid._oValue);
			}
			ret.put((long)entry.getKey(), assigns);
		}
		
		System.out.println(_hmAct2ID);
		System.out.println(ret);
		
		return ret;
	}

	public String showMemory() {
		return ((RUNTIME.totalMemory()-RUNTIME.freeMemory())/1e6d) + " / " + (RUNTIME.totalMemory()/1e6d);
	}
	
	private double extent(int dd) {
//		HashSet leaves = new HashSet();
//		_context.collectLeaves(dd, leaves);
//		System.out.println(leaves);
		
		ADDNode root = _context.getNode(dd);
		if( root instanceof ADDDNode ){
			return 0;
		}else{
			ADDINode node = (ADDINode)root; 
			System.out.println(node._dMaxLower+" " + node._dMinLower);
			return node._dMaxLower-node._dMinLower;
		}
	}
	
	public void makeOrders(ArrayList<String> sumOrder){	
		sumOrder.clear();

		Set<String> nextstatevars = _hmNextStateVars.keySet();
		
		for( String var : _alOrder ){
			if( nextstatevars.contains(var) ){
				sumOrder.add(var);
			}
		}
			
//		Collections.reverse(sumOrder);
		
		if( DEBUG0 ){
			System.out.println("Expectation order: " + sumOrder);
		}
	}
	
	private int applyNegInfConstraints(int dd, String act, TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars
			, int... save) {
		
		int ret = dd ; 
		
		int[] saved = new int[save.length+1];
		for( int i  = 0 ; i < save.length; ++i ) {
			saved[i] = save[i];
		}
		
		for(Pair<Integer,Integer> con : _hsPairConstraints ){
//			_context.getGraph(con._o2).launchViewer();
			int adder = simplify(con._o2, _hmActionMap.get(act), action_vars);
//			_context.getGraph(adder).launchViewer();
			ret = _context.applyInt(ret, adder , DD.ARITH_SUM);
			saved[save.length] = ret;
			flushCaches(saved);
		}
		
		return ret;
	}
	
	private int applyConstraintsInterleaved(int dd, String action, 
			TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars, int... save){
		int ret = dd;
		Iterator<Pair<Integer, Integer>> it = _hsPairConstraints.iterator();
		
		int[] saved = new int[save.length+1];
		for( int i = 0 ; i < save.length; ++i ){
			saved[i] = save[i];
		}
		
		while( it.hasNext() ){
			Pair<Integer, Integer> con = it.next();
			int proder = simplify(con._o1, _hmActionMap.get(action), action_vars);
			ret = _context.applyInt(ret, proder,	DD.ARITH_PROD);
//			
			int adder = simplify(con._o2, _hmActionMap.get(action), action_vars);
			ret = _context.applyInt(ret, adder, DD.ARITH_SUM);
			
			saved[save.length] = ret;
			flushCaches(saved);
		}
		
		return ret;
		
	}

	private long getSizeofPi() {
		return _context.countExactNodes(_policyDD);
	}

	private long getSizeofV() {
		return _context.countExactNodes(_valueDD);
	}

	private long getSizeofP() {
		long ret = 0;
		
		
		for( Integer i : _var2transDD.values() ){
			ret += _context.countExactNodes(i);//dual dd count
		}
		return ret;
	}

	private long getSizeofR() {
		long ret = 0;
		
		ret += _context.countExactNodes(_rewardDD); //rewarddd is zero
		
		for( Integer i : _act2rewardDD.values() ){
			ret += _context.countExactNodes(i);
		}
		
		return ret;///_act2rewardDD.size();
	}

	private long getSizeofA() {
		return _hmAct2ID.size();
	}

	public void playDD(){
		
		policy  = new Policy() {
			
			public double avg_reward = 0 ;
			public double variance = 0;//sum of squares till done
			public int horizon;
			public int numRounds;
			private long ptime;
			
			@Override
			public void roundEnd(double reward) {
//				super.roundEnd(reward);
				avg_reward += reward;
				variance += (reward*reward);
			}
			
			@Override
			public void sessionEnd(double total_reward) {
//				super.sessionEnd(total_reward);
				avg_reward /= numRounds;
				variance -= numRounds*(avg_reward*avg_reward);
				variance /= (numRounds-1);
				variance = Math.sqrt(variance);
//				System.out.println("Average reqward : " + avg_reward);
//				System.out.println("Stdev: " + variance);
				avgR = avg_reward;
				varR = variance;
				timetoplay = System.currentTimeMillis()-ptime;
			}
			
			@Override
			public void roundInit(double time_left, int horizon, int round_number, int total_rounds) {
//				super.roundInit(time_left, horizon, round_number, total_rounds);
				this.horizon = horizon;
				this.ptime = System.currentTimeMillis();
				numRounds = round_number;
			}
			
			
			
			@Override
			public ArrayList<PVAR_INST_DEF> getActions(State s) throws EvalException {
				
				ArrayList<PVAR_INST_DEF> ret = new ArrayList<PVAR_INST_DEF>();
				HashMap var2assign = new HashMap();
				
				for( Map.Entry<String, Pair<PVAR_NAME, ArrayList<LCONST>>> entry : _hmStateVars.entrySet() ){
					String name = entry.getKey();
					
					Pair<PVAR_NAME, ArrayList<LCONST>> statevar = entry.getValue();
					
					Boolean val = (Boolean)s.getPVariableAssign(statevar._o1, statevar._o2);
					var2assign.put(name, val);
//						System.out.println("State :  " + name + " : " + val );
					
				}

				int act = (int)(_context.evaluate(_policyDD, var2assign));
				
				String action = _hmID2Act.get(act);
				
				ret = _hmActionMap.get(action);
//				System.out.println(ret);
				
				return ret;				
			}
		
			public double getAvg_reward() {
				return avg_reward;
			}
			
			public double getVariance() {
				return variance;
			}
			
		};
		
		
		Client cl = new Client(this._d, this._i, this._n, policy, "localhost", 2316, "BEAVER!!!");
		
	}
	
	public void flushCaches(int... save){
		_context.clearSpecialNodes();
		
		_context.addSpecialNode(_rewardDD);
		_context.addSpecialNode(_valueDD);
		_context.addSpecialNode(_policyDD);
		
		for( int special : _alSaveNodes ){
			_context.addSpecialNode(special);
		}
		
		for( int special : save ){
			_context.addSpecialNode(special);
		}
		
		for( Pair<Integer, Integer> special : _hsPairConstraints ){
			_context.addSpecialNode(special._o1);
			_context.addSpecialNode(special._o2);
		}
		
		_context.flushCaches(false);
	}
	
	
	public void buildCPTs() throws Exception {

		_var2transDD = new TreeMap<Pair,Integer>();
		_var2observDD = new TreeMap<Pair,Integer>();
		_act2rewardDD = new TreeMap<String,Integer>();
		
		// Verify no intermediate variables
		if (_state._tmIntermNames.size() > 0)
			throw new Exception("Cannot convert to SPUDD format: contains intermediate variables");

		// Get all variables
		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> state_vars  = collectStateVars();
		action_vars = collectActionVars();
		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> observ_vars = collectObservationVars();
		
		_hmStateVars = new TreeMap<String, Pair<PVAR_NAME,ArrayList<LCONST>>>();
		_hmPrimeRemap = new HashMap<String,String>();
		_hmNextStateVars = new TreeMap<String, Pair<PVAR_NAME,ArrayList<LCONST>>>();
		
		for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : state_vars.entrySet()) {
			PVAR_NAME p = e.getKey();
			ArrayList<ArrayList<LCONST>> assignments = e.getValue();
			for (ArrayList<LCONST> assign : assignments) {
				String name = CleanFluentName(p.toString() + assign);
				_hmStateVars.put(name, new Pair(p, assign));
				_alstatevars.add(name);
				_hmNextStateVars.put(name + "'", new Pair(p, assign));
				_hmPrimeRemap.put(name, name + "'");
				_alnextstatevars.add(name+"'");
			}
		}
		
		
		//changed - 8/11/11 - does not check for constraints with respect to current state anymore - Aswin
		//constraints are imposed as very high action costs
		_hmActionMap = ActionGenerator.getLegalBoolActionMap(_state);
		for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : action_vars.entrySet()) {
			PVAR_NAME p = e.getKey();
			ArrayList<ArrayList<LCONST>> assignments = e.getValue();
			for (ArrayList<LCONST> assign : assignments) {
				String name = CleanFluentName(p.toString() + assign);
				_alactionvars.add(name);
			}
		}
//		System.out.println(_hmActionMap);
		
		_hmObservVars = new TreeMap<String, Pair<PVAR_NAME,ArrayList<LCONST>>>();
		
		
		for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : observ_vars.entrySet()) {
			PVAR_NAME p = e.getKey();
			ArrayList<ArrayList<LCONST>> assignments = e.getValue();
			for (ArrayList<LCONST> assign : assignments) {
				String name = CleanFluentName(p.toString() + assign);
				_hmObservVars.put(name+"'", new Pair(p, assign)); // SPUDD uses primed observation vars
				_alObservvars.add(name+"'");
			}
		}
		
		_alOrder = new ArrayList<String>();
		

		if( _order != ORDER_GUESS ){
			_alOrder = makeOrdering();
		}else{
			_alOrder = guessOrdering(state_vars, observ_vars, action_vars);
		}
		
		_context = new ADD(_alOrder);
		DD_ONE = _context.getConstantNode(1d);
		DD_ZERO = _context.getConstantNode(0d);
		_policyDD = DD_ZERO;
		_valueDD = DD_ZERO;
		_rewardDD = DD_ZERO;
		
		_alSaveNodes = new ArrayList<Integer>();
		_alSaveNodes.add(DD_ONE);
		_alSaveNodes.add(DD_ZERO);
		
		//constraint part - added 8/11/11 - aswin
		getConcurrencyDDFactored();
		
		
		for( EXPR constraint : _state._alConstraints ){
			if( constraint instanceof BOOL_EXPR ){
				BOOL_EXPR be = (BOOL_EXPR)constraint;
				
				addConstraint(be);
				
			}else{
				System.err.println("Constraint not tpye of bool expr");
				System.exit(1);
			}
		}
		
//		for( int con : _alConstraints ){
//			_context.getGraph(con).launchViewer();
//		}
//		Thread.sleep(100000);
		
		///////
		double rmin = 0;
		for (int iter = STATE_ITER; iter <= OBSERV_ITER; iter++) {
			
			// This loop handles both transition and observation construction
			TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> src = 
				iter == STATE_ITER ? state_vars : observ_vars;
			
			for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : src.entrySet()) {
				
				// Go through all variable names p for a variable type
				PVAR_NAME p = e.getKey();
				ArrayList<ArrayList<LCONST>> assignments = e.getValue();
				//System.out.println(_state._hmCPFs);
				
				CPF_DEF cpf = _state._hmCPFs.get(new PVAR_NAME(p.toString() + 
						(iter == STATE_ITER ? "'" : "")));
				
				HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();
				for (ArrayList<LCONST> assign : assignments) {
	
					String cpt_var = CleanFluentName(p.toString() + assign);
//					System.out.println("Processing: " + cpt_var);
	
					subs.clear();
					for (int i = 0; i < cpf._exprVarName._alTerms.size(); i++) {
						LVAR v = (LVAR)cpf._exprVarName._alTerms.get(i);
						LCONST c = (LCONST)assign.get(i);
						subs.put(v,c);
					}
					
					// This method determines all ground fluents relevant to 
					// the cpf for a variable for the given substitution 'subs'
					HashSet<Pair> relevant_vars = new HashSet<Pair>();
					EXPR cpf_expr = cpf._exprEquals;
					if (_d._bCPFDeterministic) // collectGFluents expects a distribution so convert to a Delta function if needed
						cpf_expr = new KronDelta(cpf_expr);					
					
					cpf_expr.collectGFluents(subs, _state, relevant_vars);
		
					// Filter out action vars if we are not doing a concurrent encoding
					// with actions as state variables
					relevant_vars = filterOutActionVars(relevant_vars);
					
					// Go through all actions and initialize state assignment to false 
					for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e2 : action_vars.entrySet()) {
	
						PVAR_NAME action_name = e2.getKey();
						ArrayList<ArrayList<LCONST>> action_assignments = e2.getValue();
						
						Boolean defaultval = (Boolean)_state.getDefaultValue(action_name);
	
						// Go through instances for action_name
						for (ArrayList<LCONST> action_assign : action_assignments)
							_state.setPVariableAssign(action_name, action_assign, defaultval);
	
					}
					
					// Now go through all actions, setting each one to be true in turn

					int actid = 0;
					
					
					for (Map.Entry<String,ArrayList<PVAR_INST_DEF>> e2 : _hmActionMap.entrySet()) {
						// Clear out ADD caches if required
						flushCaches();

						String action_instance = e2.getKey();
						
//						System.out.println(action_instance);
						
						ArrayList<PVAR_INST_DEF> action_list = e2.getValue();

//						System.out.println(action_instance + " " + _hmAct2ID.size());
						//added 8/11/11 - for policy construction - aswin
						_hmAct2ID.put(action_instance, actid);
						_hmID2Act.put(actid, action_instance);
						actid++;
						
						// Set all pvariables in action
						for (PVAR_INST_DEF pid : action_list){
							_state.setPVariableAssign(pid._sPredName, pid._alTerms, RDDL.BOOL_CONST_EXPR.TRUE);
						}

						//////////////////////
						
						// Build action-specific reward
						// taking into account constraint - 8/11/11 aswin
						Integer rew_fun = _act2rewardDD.get(action_instance);
						if (rew_fun == null) {
							//HashSet<Pair> rew_relevant_vars = new HashSet<Pair>();
							
							//HashMap<LVAR,LCONST> empty_sub = new HashMap<LVAR,LCONST>();
							EXPR rew_expr =  _state._reward;
							//if (_d._bRewardDeterministic) // collectGFluents expects distribution
							//	rew_expr = new DiracDelta(rew_expr);
							//rew_expr.collectGFluents(empty_sub, _state, rew_relevant_vars);
							//rew_relevant_vars = filterOutActionVars(rew_relevant_vars);

							//if (SHOW_RELEVANCE)
							//	System.out.println("Vars relevant to reward: " + rew_relevant_vars);
							//rew_fun = enumerateAssignments(new ArrayList<Pair>(rew_relevant_vars), rew_expr, empty_sub, 0);
							ArrayList<Integer> rewdd = convertAddExpr2ADD(rew_expr, true);
							
							int rdd = DD_ZERO; 
							
							for( int dd : rewdd ){
								rdd = _context.applyInt(rdd, dd, DD.ARITH_SUM);
							}
							
//							_context.getGraph(rdd).launchViewer();
//							Thread.sleep(1000000);
							
							double thisrmin = Math.abs(_context.getMin(rdd));
//							System.out.println(thisrmin);
							rmin = thisrmin > rmin ? thisrmin : rmin;
							
//							_context.getGraph(rdd).launchViewer();
//							System.in.read();
							//apply constraint will also simplify the constraint for this action
//							rdd = applyConstraints(rdd, action_list, action_vars);
//							_context.getGraph(rdd).launchViewer();
							
							_act2rewardDD.put(action_instance, rdd);
							_alSaveNodes.add(rdd);
						}							
							
						int cpt = enumerateAssignments(new ArrayList<Pair>(relevant_vars), cpf_expr, subs, 0);
						
						//build dual dd
						int true_br = _context.getVarNode(cpt_var+"'", 0.0d, 1.0d);
						true_br = _context.applyInt(true_br, cpt, DD.ARITH_PROD);
						
						int false_br = _context.getVarNode(cpt_var+"'", 1.0d, 0.0d);
						int oneminuscpt = _context.applyInt(DD_ONE, cpt, DD.ARITH_MINUS);
						false_br = _context.applyInt(false_br, oneminuscpt , DD.ARITH_PROD);
						
						cpt = _context.applyInt(true_br, false_br, DD.ARITH_SUM);
						
						_alSaveNodes.add(cpt);
						if (iter == STATE_ITER)
							_var2transDD.put(new Pair(action_instance, cpt_var+"'"), cpt);
						else
							_var2observDD.put(new Pair(action_instance, cpt_var+"'"), cpt);

						if( DEBUG4 ) {
							System.out.println("Displaying CPT for " + cpt_var+"'");
							_context.getGraph(cpt).launchViewer();
							System.out.println("Press any key");
							System.in.read();
						}
						
						//////////////////////
						
						// Unset all pvariables in action
						for (PVAR_INST_DEF pid : action_list)
							_state.setPVariableAssign(pid._sPredName, pid._alTerms, null);
						
					}
					
					
				}
			}
		}
		
//		System.out.println(" adding : " + (1+rmin));
		int adder = _context.getConstantNode((1+rmin));
		
		for( Map.Entry<String, Integer> entry : _act2rewardDD.entrySet() ){
//			System.out.println(entry.getKey());
			int oldcostdd = entry.getValue();
			int newcostdd = _context.applyInt(oldcostdd, adder, DD.ARITH_SUM);
//			newcostdd = applyConstraintsInterleaved(newcostdd, entry.getKey(), action_vars );
		//	newcostdd = applyNegInfConstraints(newcostdd, entry.getKey(), action_vars );
			entry.setValue(newcostdd);
			_alSaveNodes.remove((Integer)oldcostdd);
			_alSaveNodes.add(newcostdd);
//			_context.getGraph(oldcostdd).launchViewer();
//			_context.getGraph(newcostdd).launchViewer();
		}
		
		
//		Thread.sleep(100000);
		_rewardDD = DD_ZERO;
		_alSaveNodes.add(_rewardDD);
	}
	
	private void addConstraint(BOOL_EXPR be) throws Exception {
		HashMap<LVAR, LCONST> empty = new HashMap<LVAR, LCONST>();
		HashSet<Pair> gfluents = new HashSet<Pair>();
		be.collectGFluents(empty, _state, gfluents);
		
		IF_EXPR ife = new IF_EXPR(be, new KronDelta(new BOOL_CONST_EXPR(true)), 
				new KronDelta(new BOOL_CONST_EXPR(false)) );
		
		int zerodd = enumerateAssignments(new ArrayList<Pair>(gfluents), ife, empty	, 0 );
		
		empty.clear();
		
		ife = new IF_EXPR(be, new DiracDelta(new REAL_CONST_EXPR(0.0d)), 
				new DiracDelta(new REAL_CONST_EXPR(ADD.NEGINF)) );
		
		int neginfdd = enumerateAssignments(new ArrayList<Pair>(gfluents), ife, empty, 0);
		
		_hsPairConstraints.add(new Pair<Integer,Integer>(zerodd,neginfdd));
		
		if( DEBUG4 ) {
			System.out.println("Displaying constraint : " + be);
			_context.getGraph(zerodd).launchViewer();
			_context.getGraph(neginfdd).launchViewer();
			System.out.println("Press key...");
			System.in.read();
		}
		
		
	}
	
//	private int applyNegInfConstraints(int dd, String action, 
//			TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars) {
//		
//		int ret = dd ; 
//		for(Pair<Integer,Integer> con : _hsPairConstraints){
//			int adder = simplify(con._o2, _hmActionMap.get(action), action_vars);
//			ret = _context.applyInt(ret, adder , DD.ARITH_SUM);
//		}
//		
//		return ret;
//		
//	}

	private int simplify(int con, ArrayList<PVAR_INST_DEF> action_list, 
			TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars) {
		//simplify action vars
		int ret = con;
		
		for( Map.Entry<PVAR_NAME, ArrayList<ArrayList<LCONST>>> entry : action_vars.entrySet() ){
			//see if its in action_list
			PVAR_NAME pvar = entry.getKey();
			ArrayList<ArrayList<LCONST>> assigns = entry.getValue();
			
			for( ArrayList<LCONST> terms : assigns ){
				PVAR_INST_DEF thisone = new PVAR_INST_DEF(pvar.toString(), true, terms );
				Boolean val = action_list.contains(thisone) ? true : false;
				String actname = CleanFluentName(pvar.toString()+terms);
				int actid = (Integer)_context._hmVarName2ID.get(actname);
				
				ret = _context.restrict(ret, actid, val ? DD.RESTRICT_HIGH : DD.RESTRICT_LOW);
			}
			
		}
		
		return ret;
	}

private void getConcurrencyDDFactored() throws Exception {
		
		int ret = DD_ZERO;
		
		
		//changed to simply add indicator diagrams for each action var
		//10/19/2011 -Aswin
		
		for( String action_var : _alactionvars ) {
			int dd = _context.getVarNode(action_var, 0.0d, 1.0d);
			ret = _context.applyInt(ret, dd, DD.ARITH_SUM);
		}
		
		ret = _context.AddtoBdd(ret, _i._nNonDefActions);
		
		int zeroonedd = _context.applyInt(DD_ONE, ret, DD.ARITH_MINUS);
		
		int neginfzerodd = _context.reduceRemapLeaves(ret, remap);
		
		_hsPairConstraints.add(new Pair<Integer,Integer>(zeroonedd,neginfzerodd));
		
		if( DEBUG4 ) {
			System.out.println("Displaying concurrency constraint");
			_context.getGraph(zeroonedd).launchViewer();
			_context.getGraph(neginfzerodd).launchViewer();
			System.out.println("press key");
			System.in.read();
		}
	}

	private ArrayList<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>>  getCptOrderSortedByActionVars(TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> state_vars, 
			TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> observ_vars, TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars) throws EvalException{
		ArrayList<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>> cptOrder = 
			new ArrayList<Pair<Integer,Pair<PVAR_NAME, ArrayList<LCONST> >>>();
		
		for (int iter = STATE_ITER; iter <= OBSERV_ITER; iter++) {
			
			TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> src = 
				iter == STATE_ITER ? state_vars : observ_vars;
			
			for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : src.entrySet()) {
				
				PVAR_NAME p = e.getKey();
				ArrayList<ArrayList<LCONST>> assignments = e.getValue();
				
				CPF_DEF cpf = _state._hmCPFs.get(new PVAR_NAME(p.toString() + 
						(iter == STATE_ITER ? "'" : "")));
				
				HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();
				for (ArrayList<LCONST> assign : assignments) {
	
					String cpt_var = CleanFluentName(p.toString() + assign);
					System.out.println("Processing: " + cpt_var);
					
	
					subs.clear();
					for (int i = 0; i < cpf._exprVarName._alTerms.size(); i++) {
						LVAR v = (LVAR)cpf._exprVarName._alTerms.get(i);
						LCONST c = (LCONST)assign.get(i);
						subs.put(v,c);
					}
					
					EXPR cpf_expr = cpf._exprEquals;
					
					HashSet<Pair> relvars = new HashSet<Pair>();
					cpf_expr.collectGFluents(subs, _state, relvars);
					
					int count = 0;
					
					for( Pair pa : relvars ){
						PVAR_NAME pvar = (PVAR_NAME)pa._o1;
						ArrayList<LCONST> ass = (ArrayList<LCONST>)pa._o2;
						String name = CleanFluentName(pvar.toString()+ass);
						
						if( _state.getPVariableType(pvar) == _state.ACTION ){
							++count;
						}
					}
					
					cptOrder.add(new Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST> > >(count, new Pair<PVAR_NAME, ArrayList<LCONST> >(p, assign)));
				}
			}
		}
	
	//	System.out.println(cptOrder);
		
		Comparator<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>> cptComparator = 
			new Comparator<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>>() {
		
			public int compare(Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>> o1,
					Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>> o2) {
				
				if( o1._o1.compareTo(o2._o1) != 0 ){
					return o1._o1.compareTo(o2._o1);
				}else{
					if( o1._o2._o1.compareTo(o2._o2._o1) != 0 ){
						return o1._o2._o1.compareTo(o2._o2._o1);
					}else{
						return 0;
					}
				}
				
			}
		};
		
		Collections.sort(cptOrder, cptComparator);
	//	Collections.reverse(cptOrder);
		System.out.println(cptOrder);
		return cptOrder;
	}
private ArrayList<String> guessOrdering(TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> state_vars, 
		TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> observ_vars, TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars) throws EvalException {
	
//	String[] ord = {
//			"elevator_closed__e0", 
//			"elevator_dir_up__e0", 
//			"elevator_at_floor__e0_f0", 
//			"elevator_at_floor__e0_f1", 
//			"move_current_dir__e0", 
//			"elevator_at_floor__e0_f0'", 
//			"elevator_at_floor__e0_f2", 
//			"elevator_at_floor__e0_f1'", 
//			"elevator_at_floor__e0_f2'", 
//			"elevator_closed__e1", 
//			"elevator_at_floor__e1_f0", 
//			"elevator_at_floor__e1_f1", 
//			"elevator_dir_up__e1", 
//			"move_current_dir__e1", 
//			"elevator_at_floor__e1_f0'", 
//			"elevator_at_floor__e1_f2", 
//			"elevator_at_floor__e1_f1'", 
//			"elevator_at_floor__e1_f2'", 
//			"open_door_going_up__e0", 
//			"open_door_going_down__e0", 
//			"close_door__e0", 
//			"elevator_closed__e0'", 
//			"open_door_going_up__e1", 
//			"open_door_going_down__e1", 
//			"close_door__e1", 
//			"elevator_closed__e1'", 
//			"elevator_dir_up__e0'", 
//			"elevator_dir_up__e1'", 
//			"person_waiting_down__f2", 
//			"person_waiting_down__f0", 
//			"person_waiting_down__f1", 
//			"person_in_elevator_going_down__e0", 
//			"person_in_elevator_going_down__e0'", 
//			"person_in_elevator_going_down__e1", 
//			"person_in_elevator_going_down__e1'", 
//			"person_waiting_up__f0", 
//			"person_waiting_up__f1", 
//			"person_waiting_up__f2", 
//			"person_in_elevator_going_up__e0", 
//			"person_in_elevator_going_up__e0'", 
//			"person_in_elevator_going_up__e1", 
//			"person_in_elevator_going_up__e1'", 
//			"person_waiting_down__f0'", 
//			"person_waiting_down__f1'", 
//			"person_waiting_down__f2'", 
//			"person_waiting_up__f0'", 
//			"person_waiting_up__f1'", 
//			"person_waiting_up__f2'"};
	 
		
		ArrayList<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>> cptOrder = 
			getCptOrderSortedByActionVars(state_vars, observ_vars, action_vars);
		
		ArrayList<String> order = new ArrayList<String>();
		
//		while( cpt)
		
		for ( Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST> > > cpt : cptOrder ) {

			PVAR_NAME p = cpt._o2._o1;
			CPF_DEF cpf = _state._hmCPFs.get(new PVAR_NAME(p.toString()+ "'"));
			HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();
			ArrayList<LCONST> assign = cpt._o2._o2;
			String cpt_var = CleanFluentName(p.toString() + assign);
			subs.clear();
			for (int i = 0; i < cpf._exprVarName._alTerms.size(); i++) {
				LVAR v = (LVAR)cpf._exprVarName._alTerms.get(i);
				LCONST c = (LCONST)assign.get(i);
				subs.put(v,c);
			}
					
			EXPR cpf_expr = cpf._exprEquals;
					
			HashSet<Pair> relvars = new HashSet<Pair>();
			cpf_expr.collectGFluents(subs, _state, relvars);
					
			for( Pair pa : relvars ){
				PVAR_NAME pvar = (PVAR_NAME)pa._o1;
				ArrayList<LCONST> ass = (ArrayList<LCONST>)pa._o2;
				String name = CleanFluentName(pvar.toString()+ass);
				
				if( !order.contains(name) && _state.getPVariableType(pvar) == _state.STATE ){
					order.add(name);	
				}
						
			}
					
			for( Pair pa : relvars ){
				PVAR_NAME pvar = (PVAR_NAME)pa._o1;
				ArrayList<LCONST> ass = (ArrayList<LCONST>)pa._o2;
				String name = CleanFluentName(pvar.toString()+ass);
				
				if( !order.contains(name) && _state.getPVariableType(pvar) == _state.ACTION ){
					order.add(name);	
				}
				
			}
					
			order.add(cpt_var+"'");
					
		}
		
		System.out.println(order);
		
		if( order.size() != 2*_alstatevars.size() + _alactionvars.size() + _alObservvars.size() ) {
			System.err.println("size of ordering not equal to size of variables");
			System.out.println(order);
			System.out.println(_alstatevars);
			System.out.println(_alactionvars);
			System.out.println(_alObservvars);
			
			System.exit(1);
		}
		
		if( DEBUG0 ) {
			System.out.println("Problem has " + order.size() + " variables ");
			System.out.println("State vars : " + _alstatevars.size());
			System.out.println("Action vars : " + _alactionvars.size());
			System.out.println("Ordering : " + order);	
		}
		
		
		return order;
	}


	private ArrayList<String> makeOrdering() {
		
//		System.out.println(_hmStateVars.keySet());
//		System.out.println(_hmActionMap.keySet());
//		System.out.println(_hmNextStateVars.keySet());
//		
//		
		ArrayList<String> order = new ArrayList<String>();
//		
//		String[] ord = {"person_in_elevator_going_up__e0", "person_in_elevator_going_down__e0", 
//				"elevator_closed__e0", 
//				"elevator_at_floor__e0_f0", "elevator_dir_up__e0", "elevator_at_floor__e0_f1", "elevator_at_floor__e0_f2",
//				"person_waiting_up__f0", "person_waiting_up__f1", "person_waiting_up__f2", 
//				"person_waiting_down__f2", "person_waiting_down__f0", "person_waiting_down__f1"}; 
//		
//		return order;
		
		switch(_order){
			
			
				
			case ORDER_XPX:
				order.addAll(_alnextstatevars);
				order.addAll(_alstatevars);
				
				break;
				
			case ORDER_XXP:

				order.addAll(_alstatevars);
				order.addAll(_alnextstatevars);
			
			default:
				System.err.println("Unknown ordering...");
			
			case ORDER_INTERLEAVE:	
				
				for( String s : _alstatevars ) {
					order.add(s);
					order.add(s+"'");
				}
				break;
		}
		
		order.addAll(_alactionvars);
		
		if( DEBUG0 ) {
			System.out.println("Problem has " + order.size() + " variables");
			System.out.println("Ordering : " + order);
		}
		
		return order;
	}

				
	public ArrayList<Integer> convertAddExpr2ADD(EXPR e, boolean filter_actions) throws Exception {
		
		ArrayList<Integer> adds = new ArrayList<Integer>();
		ArrayList<Pair> exprs = getAdditiveComponents(e);
		
//		System.out.println("\n");
		for (Pair p : exprs) {
			String str = "";
			if (p._o2 instanceof RDDL.OPER_EXPR)
				str = ((RDDL.OPER_EXPR)p._o2)._op;
//			System.out.println("Found pair: " + p._o1 + " -- " + p._o2.getClass() + " / " + str + "\n" + p);
		}
		
		int ZERO_ADD = _context.getConstantNode(0d); 
		
		for (Pair p : exprs) {
			
			HashSet<Pair> relevant_vars = new HashSet<Pair>();
			HashMap subs = (HashMap)p._o1;
			EXPR e2 = ((EXPR)p._o2);
			if (_d._bRewardDeterministic) // collectGFluents expects distribution
				e2 = new DiracDelta(e2);
			else{
//				System.out.println("WARNING: May not convert additive reward correctly... check results.");
			}
			e2.collectGFluents(subs, _state, relevant_vars);
			
			if (filter_actions)
				relevant_vars = filterOutActionVars(relevant_vars);
			
			
			int add = enumerateAssignments(new ArrayList<Pair>(relevant_vars), e2, subs, 0);
			if (add != ZERO_ADD)
				adds.add(add);
		}
//		System.out.println("Done processing additive expression");
		
		return adds;
	}
	
	// TODO: Make this recursive (append subs, ...)
	// TODO: Make an expression to additive expression converter to be called in buildCPTs()
	// Returns Pair(HashMap subs, EXPR e)
	public ArrayList<Pair> getAdditiveComponents(EXPR e) throws Exception {

		ArrayList<Pair> ret = new ArrayList<Pair>();
		
		if (e instanceof OPER_EXPR && ((OPER_EXPR)e)._op == OPER_EXPR.PLUS) {

			OPER_EXPR o = (OPER_EXPR)e;

			//System.out.println("\n- Oper Processing " + o._e1);
			//System.out.println("\n- Oper Processing " + o._e2);
			
			ret.addAll(getAdditiveComponents(o._e1));
			ret.addAll(getAdditiveComponents(o._e2));
						
		} else if (e instanceof AGG_EXPR && ((AGG_EXPR)e)._op == AGG_EXPR.SUM) {
			
			AGG_EXPR a = (AGG_EXPR)e;
			
			ArrayList<ArrayList<LCONST>> possible_subs = _state.generateAtoms(a._alVariables);
			HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();

			//System.out.println("\n- Sum Processing " + a);

			// Evaluate all possible substitutions
			for (ArrayList<LCONST> sub_inst : possible_subs) {
				for (int i = 0; i < a._alVariables.size(); i++) {
					subs.put(a._alVariables.get(i)._sVarName, sub_inst.get(i));
				}
				
				// Note: we are not currently decomposing additive structure below a sum aggregator
				ret.add(new Pair(subs.clone(), a._e));			
				
				subs.clear();
			}

		} else {
			//System.out.println("\n- General Processing " + e);
			HashMap<LVAR,LCONST> empty_subs = new HashMap<LVAR,LCONST>();
			ret.add(new Pair(empty_subs, e));
		}
		
		return ret;
	}
	
	private HashSet<Pair> filterOutActionVars(HashSet<Pair> relevant_vars) {
		HashSet<Pair> new_vars = new HashSet<Pair>();
		for (Pair p : relevant_vars)
			if (_state.getPVariableType((PVAR_NAME)p._o1) != State.ACTION)
				new_vars.add(p);
		return new_vars;
	}

	public int enumerateAssignments(ArrayList<Pair> vars, 
			EXPR cpf_expr, HashMap<LVAR,LCONST> subs, int index) 
		throws EvalException {
		return enumerateAssignments(vars, cpf_expr, subs, index, _context.getConstantNode(1d));
	}
	
	public int enumerateAssignments(ArrayList<Pair> vars, 
			EXPR cpf_expr, HashMap<LVAR,LCONST> subs, int index, int cpt) 
		throws EvalException {

		// Need to build an assignment ADD on the way down, use it 
		// at leaf to add to current CPT and return result
		
		// Recurse until index == vars.size
		if (index >= vars.size()) {
			
			// At this point, all state and action variables are set
			// Just need to get the distribution for the appropriate CPT
			RDDL.EXPR e = cpf_expr.getDist(subs, _state);
			double prob_true = -1d;
			//System.out.println("RDDL.EXPR: " + e);
			if (e instanceof KronDelta) {
				EXPR e2 = ((KronDelta)e)._exprIntValue;
				if (e2 instanceof INT_CONST_EXPR)
					// Should either be true (1) or false (0)... same as prob_true
					prob_true = (double)((INT_CONST_EXPR)e2)._nValue;
				else if (e2 instanceof BOOL_CONST_EXPR)
					prob_true = ((BOOL_CONST_EXPR)e2)._bValue ? 1d : 0d;
				else
					throw new EvalException("Unhandled KronDelta argument: " + e2.getClass()); 
			} else if (e instanceof Bernoulli) {
				prob_true = ((REAL_CONST_EXPR)((Bernoulli)e)._exprProb)._dValue;
			} else if (e instanceof DiracDelta) {
				// NOTE: this is not a probability, but rather an actual value
				//       (presumably for the reward).  This method is a little
				//       overloaded... need to consider whether there will be
				//       any problems arising from this overloading.  -Scott
				prob_true = ((REAL_CONST_EXPR)((DiracDelta)e)._exprRealValue)._dValue;
			} else
				throw new EvalException("Unhandled distribution type: " + e.getClass());
			
			// Now build CPT for action variables
			return _context.scalarMultiply(cpt, prob_true);

		} else {
			PVAR_NAME p = (PVAR_NAME)vars.get(index)._o1;
			ArrayList<LCONST> terms = (ArrayList<LCONST>)vars.get(index)._o2;
			String var_name = CleanFluentName(p._sPVarName + terms + (p._bPrimed ? "\'" : "")); 
			//System.out.println(var_name);

			// Set to true
			_state.setPVariableAssign(p, terms, RDDL.BOOL_CONST_EXPR.TRUE);
			int high = _context.getVarNode(var_name, 0d, 1d);
			int ret_high = enumerateAssignments(vars, cpf_expr, subs, index + 1, _context.applyInt(cpt, high, ADD.ARITH_PROD));

			// Set to false
			_state.setPVariableAssign(p, terms, RDDL.BOOL_CONST_EXPR.FALSE);
			int low  = _context.getVarNode(var_name, 1d, 0d);
			int ret_low = enumerateAssignments(vars, cpf_expr, subs, index + 1, _context.applyInt(cpt, low, ADD.ARITH_PROD));
			
			// Unassign
			_state.setPVariableAssign(p, terms, null);
			
			// Sum both sides of the returned CPT and return it
			return _context.applyInt(ret_high, ret_low, ADD.ARITH_SUM);
		}
	}
		
	public TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> collectStateVars() 
		throws EvalException {
		
		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> state_vars = 
			new TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>>();
	
		for (PVAR_NAME p : _state._alStateNames) {
			ArrayList<ArrayList<LCONST>> gfluents = _state.generateAtoms(p);
			state_vars.put(p, gfluents);
		}
		
		return state_vars;
	}
	
	public TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> collectActionVars() 
		throws EvalException {
		
		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> action_vars = 
			new TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>>();
	
		for (PVAR_NAME p : _state._alActionNames) {
			ArrayList<ArrayList<LCONST>> gfluents = _state.generateAtoms(p);
			action_vars.put(p, gfluents);
		}
		
		return action_vars;
	}
	
	public TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> collectObservationVars() 
		throws EvalException {

		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> observ_vars = 
			new TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>>();
	
		for (PVAR_NAME p : _state._alObservNames) {
			ArrayList<ArrayList<LCONST>> gfluents = _state.generateAtoms(p);
			observ_vars.put(p, gfluents);
		}
		
		return observ_vars;
	}
	
	private int applyZeroOneConstraints(int dd, String act, TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars
			, int... save) {
		
		int ret = dd ; 
		
		int[] saved = new int[save.length+1];
		for( int i  = 0 ; i < save.length; ++i ) {
			saved[i] = save[i];
		}
		
		for(Pair<Integer,Integer> con : _hsPairConstraints ){
			int adder = simplify(con._o1, _hmActionMap.get(act), action_vars);
			ret = _context.applyInt(ret, adder , DD.ARITH_PROD);
			saved[save.length] = ret;
			flushCaches(saved);
		}
		
		return ret;
	}
		
	public static String CleanFluentName(String s) {
		s = s.replace("[", "__");
		s = s.replace("]", "");
		s = s.replace(", ", "_");
		s = s.replace(',','_');
		s = s.replace(' ','_');
		s = s.replace('-','_');
		s = s.replace("()","");
		s = s.replace("(", "__");
		s = s.replace(")", "");
		if (s.endsWith("__"))
			s = s.substring(0, s.length() - 2);
		return s;
	}
	
	public static void main(String[] args) throws Exception {
//		new ValueIteration("/scratch/final_comp/rddl/sysadmin_mdp.rddl", 
//				"/scratch/final_comp/rddl/sysadmin_uniring_5_combined.rddl", 
//				0.1, ValueIteration.ORDER_GUESS, -1, "./");
		
//		new ValueIteration("/scratch/final_comp/rddl/elevators_mdp.rddl",
//				"/scratch/final_comp/rddl/elevators_inst_mdp__1.rddl",
//				0.1, ValueIteration.ORDER_GUESS, -1, "./");
		
//		new ValueIteration("/scratch/final_comp/rddl/crossing_traffic_mdp.rddl",
//				"/scratch/final_comp/rddl/crossing_traffic_inst_mdp__1.rddl",
//				0.1, ValueIteration.ORDER_AXPX, -1, "./");
		
		BlockingQueue<Pair<ADD,Integer>> bq = new ArrayBlockingQueue<Pair<ADD,Integer>>(40);
		
		ValueIteration vi = new ValueIteration(args[0], args[1],
				Double.parseDouble(args[2]), ValueIteration.ORDER_INTERLEAVE,
				-1, "./", null); 
		
		Thread worker = new Thread(vi, "Spudd");
		worker.start();
		
		worker.join(2*60*60*1000);
//("./rddl/traffic_mdp.rddl",
//				"./rddl/traffic_inst_mdp__1.rddl",
//				0.1, ValueIteration.ORDER_AXPX, -1, "./");
		
		System.out.println(vi);
	}

	@Override
	public String toString() {
		return "ValueIteration [presolve=" + this.presolve + ", \navgR="
				+ this.avgR + ", \nvarR=" + this.varR + ", \npostsolve="
				+ this.postsolve + ", \nrInfo=" + this.rInfo + "]";
	}

}
